﻿// Ignore this define for the moment; it would add some windows shutdown capabilities we don't know if needed. 
// Downside: Breaks mono due to .DllImport("kernel32.dll"
//#define WINDOWS_BUILD

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
using log4net;

[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]

namespace AcServerWrapper
{
    // Until we know where to go, we'll wait define a class model for the process start things.
    // Maybe we will need the possibility to start multiple a) different AcServers or b) the same one multiple times (e.g. start/stop/restart)
    // or c) a windows service or d) alltogether. For the moment this code is regarded as fine
    class Program
    {
        // There is quite an usage of different loggers, this one is for the wrapper itself. So to log, you should only use this one
        static ILog log = LogManager.GetLogger("wrapper");
        static AcServerLogProcessor AcLogger = new AcServerLogProcessor();
        static object childProcessLock = new object();
        static Process Child = null;

        static void Main(string[] args)
        {
            try
            {
                log.Info("AcServerWrapper: Startup");

                // Let's load some logfile-processing rules
                var rulesLoaded = AcLogger.LoadRules(FilenameHelper.GetWrapperPath(ConfigurationManager.AppSettings["rules"]));
                log.Debug("AcServer: " + rulesLoaded + " Rules loaded");

                // If availible, start a pre-Start Process and wait for exit. This could by something like svn update (for the server-inis)
                if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["preStartExecution"]))
                    TryPrestartExection();
                else
                    log.Debug("No preStartExection configured, skipping");

                // Do we have a parameter? That'll be our config, which is overwriting cfg/ ini-files
                if (args != null && args.Length > 0)
                    TryCopyCustomInis(args[0]);
                else
                    log.Debug("No arguments, skipping CopyCustomIni-step");

                // StartProcess
                var sw = new Stopwatch();
                lock (childProcessLock)
                {
                    sw.Start();
                    log.Debug("AcServerWrapper: StartAcServer");
                    Child = StartAcServer();
                }
                
                #region Windows-Specific stuff for closing the ChildProcess when killed (works until TaskManager->Processes->Kill)
#if WINDOWS_BUILD

                handler = new ConsoleEventDelegate(ConsoleEventCallback);
                SetConsoleCtrlHandler(handler, true);
#endif
                #endregion


                log.Debug("AcServerWrapper: Process "+ Child.Id + " started, now waiting for exit");
                // Wait for Process
                Child.WaitForExit();

                log.Info("AcServerWrapper: Exited (Was up:  " + sw.Elapsed + ")");
            }
            catch (Exception ex)
            {
                log.Fatal("AcServerWrapper failed", ex);
            }
        }

        #region more Windows-specific stuff
#if WINDOWS_BUILD
        static ConsoleEventDelegate handler;   // Keeps it from getting garbage collected
        // Pinvoke
        private delegate bool ConsoleEventDelegate(int eventType);
        [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);

        static bool ConsoleEventCallback(int eventType)
        {
            if (eventType == 2)
            {
                lock (childProcessLock)
                {
                    if (Child != null)
                    {
                        try
                        {
                            Child.Kill();
                            Child = null;
                        }
                        catch (Exception ex)
                        {
                            LogManager.GetLogger("error").Fatal(ex);
                        }
                    }
                }
                LogManager.GetLogger("acw").Debug("ConsoleEventCallback event == 2");
                Console.WriteLine("Console window closing, death imminent");
            }
            return false;
        }
#endif
        #endregion

        private static void TryPrestartExection()
        {
            try
            {
                var file = ConfigurationManager.AppSettings["preStartExecution"];
                var directory = Path.GetDirectoryName(file);

                var process = new Process()
                {
                    StartInfo = new ProcessStartInfo()
                    {
                        FileName = file,
                        Arguments = ConfigurationManager.AppSettings["preStartExecutionParams"],
                    }
                };

                if (!string.IsNullOrEmpty(directory))
                    process.StartInfo.WorkingDirectory = directory;

                log.Debug("Try TryPrestartExection ' " + process.StartInfo.FileName +" " + process.StartInfo.Arguments + "'");

                process.Start();



                if (!Convert.ToBoolean(ConfigurationManager.AppSettings["preStartExecutionDoNotWait"]))
                    process.WaitForExit();
            }
            catch (Exception ex)
            {
                log.Error("Prestart Execution failed", ex);
            }
        }

        private static Process StartAcServer()
        {
            // Now we basically start a new process and optionally redirect and process std-out and err-out

            // acServerLocation may be relative or absolute; relative has to be added to Assembly-Directory (so we start where the wrapper is stored, think of executing wrapper/AcServerWrapper.exe)
            var process = new Process()
            {
                StartInfo = new ProcessStartInfo()
                {
                    WorkingDirectory = FilenameHelper.ServerDirectory,
                    FileName = FilenameHelper.ServerExecutable,
                    Arguments = ConfigurationManager.AppSettings["execParams"],
                    RedirectStandardOutput = Convert.ToBoolean(ConfigurationManager.AppSettings["redirectLog"]),
                    RedirectStandardError = Convert.ToBoolean(ConfigurationManager.AppSettings["redirectErrors"]),
                    UseShellExecute = false
                }
            };

            if(!File.Exists(Path.Combine(process.StartInfo.WorkingDirectory, process.StartInfo.FileName)))
                throw new Exception("StartAcServer: Executable File not found: " + Path.Combine(process.StartInfo.WorkingDirectory, process.StartInfo.FileName));

            log.Info("Start " + Path.Combine(process.StartInfo.WorkingDirectory, process.StartInfo.FileName));
            process.OutputDataReceived += process_OutputDataReceived;
            process.ErrorDataReceived += process_ErrorDataReceived;

            process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            return process;
        }

        private static void TryCopyCustomInis(string server_config)
        {
            log.Info("TryCopyCustomInis " + server_config);
            // This should be a thing - we excpect server_cfg.ini and entry_list.ini relative to acServerLocation/cfg/{server_config}/
            var live_config_path = FilenameHelper.GetServerPath("cfg");
            log.Debug("live_config_path is "+ live_config_path);
            var custom_config_path = Path.Combine(live_config_path, server_config);
            log.Debug("custom_config_path is " + custom_config_path);
            var custom_server_cfg_ini = Path.Combine(custom_config_path, "server_cfg.ini");
            log.Debug("custom_server_cfg_ini is " + custom_server_cfg_ini);
            var custom_entry_list_ini = Path.Combine(custom_config_path, "entry_list.ini");
            log.Debug("custom_entry_list_ini is " + custom_entry_list_ini);

            if (!File.Exists(custom_server_cfg_ini))
                throw new FileNotFoundException(custom_server_cfg_ini);
            if (!File.Exists(custom_entry_list_ini))
                throw new FileNotFoundException(custom_entry_list_ini);

            // We should protect users from overwriting their live config
            if (!Convert.ToBoolean(ConfigurationManager.AppSettings["iniOverwriteAllowed"]))
                log.Error("You tried to pass a parameter '" + server_config + "', but didn't allow Overwriting you config in the AcServerWrapper.exe.config");
            else
            {
                // Now we try to copy both to cfg/
                File.Copy(custom_server_cfg_ini, Path.Combine(live_config_path, "server_cfg.ini"), true);
                File.Copy(custom_entry_list_ini, Path.Combine(live_config_path, "entry_list.ini"), true);
                log.Debug("2 Files copied");
            }
        }

        private static void CreateSampleXmlFile(string filename)
        {
            var collection = new RuleConfiguration();

            var ruleSetChat = new RuleSet() { Name = "chat" };
            ruleSetChat.Rules.Add(new Rule() { ComparisonType = Rule.ComparisonTypes.StartsWith, Value = "CHAT " });
            collection.RuleSets.Add(ruleSetChat);

            collection.Serialize(filename);
        }

        private static void TestLogs(string p)
        {
            var lines = System.IO.File.ReadAllLines(p);
            foreach (var line in lines)
            {
                AcLogger.Process(line);
            }
        }

        private static void TestLogs()
        {
            LogManager.GetLogger("log").Debug("Debug, should be seen in the logfile");
            LogManager.GetLogger("session").Debug("Session debug, should be seen in the logfile and the session.log");
            LogManager.GetLogger("laptimes").Debug("laptimes debug, should be seen in the logfile and the laptimes.log");
            LogManager.GetLogger("error").Debug("Debug, shouldn't be seen in the error log");
            LogManager.GetLogger("error").Error("This is a failure");
            LogManager.GetLogger("error").Fatal("This is a fatality");
        }

        static void process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            LogManager.GetLogger("errors").Fatal(e.Data);
        }

        static void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            AcLogger.Process(e.Data);
        }
    }
}
